
from Network import *

import time

the_network = Network()

@the_network.remote_procedure(authority=Network.NET_CLIENT, is_timestamped=True)
def giveCommand(command):
    print "Giving fake command: ", command, " with latency: ", giveCommand.network_latency

@the_network.remote_procedure(authority=Network.NET_SERVER, is_timestamped=True)
def detonate():
    print "Detonating with latency: ", detonate.network_latency

# Example-only entity management
the_entities = {}
class Entity(object):
    def __init__(self, name):
        self.name = name
        
        self.synchAttribute('name', Network.SYNCH_ON_REPLICATE_ONLY)
        
        # Register the entity
        the_entities[name] = self
Entity = the_network.REMOTE_OBJECT(authority=Network.NET_SERVER)(Entity)

#@the_network.remote_object(authority=Network.NET_SERVER, is_timestamped=True) # Python >= 2.6 only
class MyEntity(Entity):
    """This is the docstring for MyEntity
    """
    def __init__(self, name, health=100, pos=(0.0, 0.0)):
        Entity.__init__(self, name)

        self.health = health
        self.pos = pos

        """
        self.synchAttribute('health', Network.SYNCH_ON_REPLICATE_ONLY)
        self.synchAttribute('pos', Network.SYNCH_CONTINUOUSLY, 0.1)

        Could we use state management within the __init__ method to emulate the private: syntax?
        
        self.texture = texture
        self.is_sleeping = False

        self.beginSynchOnReplicateAttrs()
        self.display_name = "Unnamed"
        self.health = 100
        self.team = 1
        self.num_grenades = 4
        
        self.beginContinuouslySynchedAttrs()
        self.pos = pos
        self.angle = angle

        self.synchOnReplication('display_name', 'health', 'team', 'num_grenades')
        self.synchContinuously('pos', tolerance=0.1)
        self.synchContinuously('angle', tolerance=3)
        
        class SynchOnReplication:
            pass

        class SynchContinuously:
            pass
        """
        
        """
        #Example entity attributes
        self.name                       # Remote, static
        self.pos = b2Vec2(1.0, 2.0)     # Remote, dynamic, dead reckoned
        self.angle = math.radians(12)   # Remote, dynamic, dead reckoned
        self.health = 100               # Remote, dynamic, no extrapolation, no tolerance
        self.team = 1                   # Remote, static
        self.num_grenades = 4           # Remote, dynamic, no extrapolation, no tolerance
        self.texture = some_texure      # Local
        self.is_sleeping = False        # Local (but may affect synchronisation procedure)

        authority
            e = MyEntity(*local_attrs_0, *remote_attrs_0)

        Player joins as client1
        Notification sent to server
        Client1 loads all *local-only\static* level elements
        Server sends remote object list to client1
        Client1 replicates remote objects
        """
        
        #self.deadreckon(self.health, extrapolation_function=something, tolerance=something)

        # TODO: Think about how dead reckoning should work with physics. Perhaps just ignore
        #        the extrapolation_function, and have the authority run its own Box2D world.
        #       Could just have an entire (networked, physical) client world simulated alongside the authoritative simulation.

        # What if the server was always the authority, and the clients ran latency seconds behind it?

        # authority keeps track of what frame number it's up to, and send it to the non-auth
        # the non-auth receives the frame number and the latency. It calcs auth_frame - my_frame
        
        #on authority:
        #   world
        #   remote_world
        
        #def createUpdate():
        #    for attr in self._remote_dynamic_attributes_:
                
        #on authority:
        #    MyEntity update sent, comprising attribute values, v, and their derivatives, v', at time t0.
        #    Each non-authority now thinks that at time t=t0+dt, MyEntity's attribute values are F(dt, v, v', ...), where F is an agreed-upon
        #     extrapolation function.
        #    The authority now knows what the non-authorities are thinking, as well as what it knows to be the truth, which may differ if, e.g., an
        #     attribute value has recently changed as a result of user input.
        #    During some tick after t0, if the authority notices that the difference between the true values and the non-authority values of some attributes
        #     is greater than a predetermined tolerance level, then it will issue an update containing those attributes.
        #Notes:
        # Non-authorities are given (a good estimate of) the value of t0 in the form of the latency returned by timestamped RPCs.
        # This procedure describes extrapolation of remote attribute values. Interpolation is applied separately by the non-authorities to smooth out
        #  the apparent change of attribute values.
        # F will likely be several algorithms, picked on a per-attribute basis. e.g., F[pos](dt, v, v') = v+v'dt, F[health](dt, v) = v
        
        # Remote attributes come in two flavours:
        #  static:  Synchronised only when the object is created, and thereafter assumed not to change
        #  dynamic: Synchronised constantly

    @the_network.remote_procedure(authority=Network.NET_SERVER)
    def sayHelloTo(self, whom):
        print "MyEntity says hello to ", whom
print 'MyEntity.__doc__ = ', MyEntity.__doc__
# Python < 2.6
MyEntity = the_network.REMOTE_OBJECT(authority=Network.NET_SERVER)(MyEntity)

print 'MyEntity.__doc__ = ', MyEntity.__doc__

what = raw_input('(C)lient or (S)erver? ')
if what[0] == 'C' or what[0] == 'c':
    the_network.startAsClient()
else:
    the_network.startAsServer()

raw_input('Press enter to begin')

# Below is a crappy test of the system 

if the_network.node_type == Network.NET_SERVER:
    m = MyEntity("entity1", health=90, pos=(1.0, 0.0))

giveCommand('jump')
detonate()

last_time = time.clock()
while True:
    if time.clock() - last_time >= 4.0:
        # Every 4 seconds
        giveCommand('jump')
        detonate()
        last_time = time.clock()
        
        the_entities["entity1"].sayHelloTo('me')
        
        if the_network.node_type == Network.NET_SERVER:
            # This obviously can only be called once, and will cause an error when it is called the second time
            # ***EXPECT A CRASH HERE*** (But it shows off the nice error messages.)
            #the_entities["entity1"].removeFromNetwork()
            pass
    
    the_network.handleIncomingPackets()
    PyRakNet.Sleep(10)
